package jmine.tec.rpc.server.representers;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import jmine.tec.rpc.common.provider.RestServiceProvider;
import jmine.tec.rpc.server.representation.BaseRepresentationFactory;
import jmine.tec.rpc.server.representation.ReflectionFallbackRepresenterFactory;
import jmine.tec.rpc.server.representation.RepresentationFactory;
import jmine.tec.rpc.server.representation.representers.NoOperationRepresenter;
import jmine.tec.rpc.server.representation.representers.collections.ListRepresenter;
import jmine.tec.rpc.server.representation.representers.collections.MapRepresenter;
import jmine.tec.rpc.server.representation.representers.collections.SetRepresenter;
import jmine.tec.rpc.server.representation.representers.lazy.LazyFallbackRepresenterFactory;
import jmine.tec.rpc.server.representation.representers.lazy.LazyListPropertyRepresenter;
import jmine.tec.rpc.server.representation.representers.lazy.LazyMapPropertyRepresenter;
import jmine.tec.rpc.server.representation.representers.reflection.AnyPropertyRepresenter;
import jmine.tec.rpc.server.representation.representers.reflection.ListPropertyRepresenter;
import jmine.tec.rpc.server.representation.representers.reflection.MapPropertyRepresenter;
import jmine.tec.rpc.server.representation.representers.reflection.NoOperationPropertyRepresenter;
import jmine.tec.rpc.server.representation.representers.reflection.PropertyRepresenter;
import jmine.tec.rpc.server.representation.representers.reflection.SetPropertyRepresenter;
import jmine.tec.test.Builder;
import bancosys.tec.utils.date.Date;

/**
 * Builder de RepresentationFactory
 * 
 * @author lundberg
 */
public class LazyRepresentationFactoryBuilder implements Builder<RepresentationFactory> {

    private RestServiceProvider provider;

    /**
     * {@inheritDoc}
     */
    public RepresentationFactory build() {
        BaseRepresentationFactory factory = new BaseRepresentationFactory();

        LazyFallbackRepresenterFactory fallback = new LazyFallbackRepresenterFactory();
        fallback.setFallback(this.createReflectionFallback(factory));
        fallback.setProvider(this.provider);

        factory.setFallback(fallback);

        factory.register(Collections.singleton(sameClassNoOpRepresenter(Integer.class)));
        factory.register(Collections.singleton(sameClassNoOpRepresenter(Long.class)));
        factory.register(Collections.singleton(sameClassNoOpRepresenter(Boolean.class)));
        factory.register(Collections.singleton(sameClassNoOpRepresenter(BigDecimal.class)));
        factory.register(Collections.singleton(sameClassNoOpRepresenter(Date.class)));
        factory.register(Collections.singleton(sameClassNoOpRepresenter(Class.class)));
        factory.register(Collections.singleton(sameClassNoOpRepresenter(String.class)));
        factory.register(Collections.singleton(subclassNoOpRepresenter(Serializable.class, Long.class)));
        factory.register(Collections.singleton(subclassNoOpRepresenter(Serializable.class, String.class)));
        factory.register(Collections.singleton(new ListRepresenter()));
        factory.register(Collections.singleton(new SetRepresenter()));
        factory.register(Collections.singleton(new MapRepresenter()));

        return factory;
    }

    /**
     * @param factory factory
     * @return ReflectionFallbackRepresenterFactory
     */
    protected ReflectionFallbackRepresenterFactory createReflectionFallback(BaseRepresentationFactory factory) {
        ReflectionFallbackRepresenterFactory fallback = new ReflectionFallbackRepresenterFactory();
        fallback.setFactory(factory);
        List<PropertyRepresenter> propertyRepresenters = new ArrayList<PropertyRepresenter>();
        propertyRepresenters.add(new NoOperationPropertyRepresenter());
        propertyRepresenters.add(new LazyListPropertyRepresenter());
        propertyRepresenters.add(new LazyMapPropertyRepresenter());
        propertyRepresenters.add(new ListPropertyRepresenter());
        propertyRepresenters.add(new SetPropertyRepresenter());
        propertyRepresenters.add(new MapPropertyRepresenter());
        propertyRepresenters.add(new AnyPropertyRepresenter());
        fallback.setPropertyRepresenters(propertyRepresenters);
        return fallback;
    }

    /**
     * Cria um representador que não existe operação que converte em uma mesma classe.
     * 
     * @param <E> tipo de objeto
     * @param representationClass representationClass
     * @return representer
     */
    private static <E> NoOperationRepresenter<E, E> sameClassNoOpRepresenter(Class<E> representationClass) {
        return new NoOperationRepresenter<E, E>(representationClass, representationClass);
    }

    /**
     * Cria um representador que não existe operação que converte em uma mesma classe.
     * 
     * @param <R> tipo de representação
     * @param <E> tipo de entidade representada
     * @param representationClass representationClass
     * @param representedClass representedClass
     * @return representer
     */
    private static <R, E extends R> NoOperationRepresenter<R, E> subclassNoOpRepresenter(Class<R> representationClass,
            Class<E> representedClass) {
        return new NoOperationRepresenter<R, E>(representationClass, representedClass);
    }

    /**
     * RepresentationFactoryBuilder básico
     * 
     * @return RepresentationFactoryBuilder
     */
    public static LazyRepresentationFactoryBuilder aLazyRepresentationFactory() {
        return new LazyRepresentationFactoryBuilder();
    }

    /**
     * @param aProvider the provider to set
     * @return this
     */
    public LazyRepresentationFactoryBuilder with(RestServiceProvider aProvider) {
        this.provider = aProvider;
        return this;
    }
}
